#define PrynInternal
#define PrynImplementation
#include <pryn.h>
#include <stdio.h>
#include <stdarg.h>

extern PrynResult prynStatePlatformAllocate (PrynState *self, uintptr_t length, void **output);
extern PrynResult prynStatePlatformFree (PrynState *self, void *pointer);
extern PrynResult prynDefaultPrintFunction (PrynState *state, const PrynString *value);
extern void *prynStatePlatformPlatform ();

// Build a function for a state, ensuring it's non-zero before executing body.
#define PrynStateFunction(BODY) \
	do { \
		PrynStartFunctionHasState (); \
		prynAssert (state, NullArgument); \
		BODY \
		PrynEndFunction (); \
	} while (0)

static PrynResult functionPrintNull (PrynState *state, const PrynString *text)
{
	return prynDefaultPrintFunction (state, text);
	return PrynResultDone;
}

PrynExport (PrynResult, prynStateCreate, (PrynState **state))
{
	return prynStateCreateWithAllocator (state, &prynStatePlatformAllocate, &prynStatePlatformFree, prynStatePlatformPlatform ());
}

PrynExport (PrynResult, prynStateCreateWithAllocator, (PrynState **output, PrynAllocateFunction allocate, PrynFreeFunction free, void *platform))
{
	PrynState sstate; // Static state used for allocating heap state.
	PrynState *state; // Heap state to be output.
	PrynResult result;

	if (output) *output = 0;
	if (!output || !allocate || !free) return PrynResultNullArgument;
	
	prynSetMemory (&sstate, 0, sizeof (PrynState));
	sstate.pCommonObject.pType = PrynObjectTypeState;
	sstate.pPrintFunction = &functionPrintNull;
	sstate.pAllocateFunction = allocate;
	sstate.pFreeFunction = free;
	sstate.pPlatform = platform;
	
	if ((result = allocate (&sstate, sizeof (PrynState), (void **) &state)) < 0) return result;
	*state = sstate;
	*output = state;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynStateTags, (PrynState *state, PrynTagList **output))
{
	if (output) *output = 0;
	PrynStateFunction ({
		if (output) *output = &state->pCommonObject.pTags;
	});
}

PrynExport (PrynResult, prynStateAddLibraryPath, (PrynState *state, const PrynString *path))
{
	PrynStartFunctionHasState ();
	PrynString *search, *end;
	PrynString copy;

	prynAssert (state && path, NullArgument);

	// Return if it's already in the search list.
	for (search = state->pPaths, end = search + state->pPathCount; search < end; search ++)
		if (PrynIsTrue (prynStringEqual (search, path)))
			prynReturnDone ();

	// It's not; load the path then add after success.
	prynCall (prynStateLoadLibraries (state, path));
	prynCall (prynReallocate (state, sizeof (*state->pPaths) * (state->pPathCount + 1), &state->pPaths));
	prynStringDuplicate (&copy, state, path);
	state->pPaths [state->pPathCount ++] = copy;

	PrynEndFunction ();
}

static void StateResetMarks (PrynState *state)
{
	PrynLibrary *library;
	PrynMediator *mediator;
	PrynFactory *factory;
	PrynType * type;

	#define markFalse(name, Names) for (name = state->p##Names.first; name; name = name->pNext) name->pCommonObject.pMark = false

	markFalse (library, Libraries);
	markFalse (mediator, Mediators);
	markFalse (factory, Factories);
	markFalse (type, Types);
	#undef markFalse
}

PrynExport (PrynResult, prynStateLoadLibraries, (PrynState *state, const PrynString *path))
{
	PrynStartFunctionHasState ();
	PrynString extension;
	PrynFileDescription file;
	PrynLibrary *library, *libraryNext;
	PrynMediator *mediator, *mediatorNext;
	PrynType *type, *typeNext;
	PrynFactory *factory, *factoryNext;

	prynAssert (state && path, NullArgument);
	prynStringLiteral (&extension, ".dll");
	StateResetMarks (state);
		
	// Scan the path. For each file, create the library. If the time is different, load it to reset names and descriptions.
	for (result = prynFileBeginIteration (state, &file, path); !result; result = prynFileNextIteration (&file))
		if (!prynStringEqual (&file.extension, &extension))
		{
			prynCall (prynLibraryCreate (&library, state, &file.fullname));
			library->pCommonObject.pMark = true;
			if (!PrynIsTrue (prynTimeEqual (&library->pModified, &file.modified)))
			{
				library->pModified = file.modified;
				prynCall (prynLibraryLoad (library));
			}
		}

	// Destroy any libraries which are in the path but no longer exist.
	for (library = state->pLibraries.first; library; library = libraryNext)
	{
		libraryNext = library->pNext;
		if (PrynIsTrue (prynLibraryInPath (library, path)) && !library->pCommonObject.pMark)
			prynLibraryDestroy (library);
	}

	// Destroy any objects whose libraries are marked but they are not, meaning they no longer exist.
	#define filterUnmarked(name, Name, Names) \
		for (name = state->p##Names.first; name; name = name##Next) \
		{ \
			name##Next = name->pNext; \
			if (name->pLibrary && name->pLibrary->pCommonObject.pMark&& name->pLibrary->pPlatform  && !name->pCommonObject.pMark) \
				pryn##Name##Destroy (name); \
			\
		}

	filterUnmarked (mediator, Mediator, Mediators)
	filterUnmarked (type, Type, Types)
	filterUnmarked (factory, Factory, Factories)
	#undef filterUnmarked

	PrynEndFunctionAfterCommon ({
		prynFileEndIteration (&file);
	});
}

PrynExport (PrynResult, prynStateSetPrintFunction, (PrynState *state, PrynPrintFunction print))
{
	if (!state) return PrynResultNullArgument;
	if (!print) print = &functionPrintNull;
	state->pPrintFunction = print;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynPrintMemoryReport, (PrynState *state))
{
	if (!state) return PrynResultNullArgument;
#ifndef PrynAllocationTracker
	return PrynResultDone;
#else /* PrynAllocationTracker */
	prynPrintFormat (state, 
		"Pryn memory state:\n"
		"\t%d byte(s) allocated\n"
		"\t%d maximum byte(s) allocated\n", state->pAllocatedCurrent, state->pAllocatedMaximum);
	return PrynResultSuccess;
#endif /* PrynAllocationTracker */
}

PrynExport (PrynResult, prynPrintMemoryUpdates, (PrynState *state))
{
	if (!state) return PrynResultNullArgument;
#ifndef PrynAllocationTracker
	return PrynResultDone;
#else
	PrynResult result = prynPrintMemoryReport (state);
	if (result < 0) return result;
	
	size_t mask = 1 << 31;
	
	prynDListEachP (PrynAllocation, allocation, state->pAllocations)
	{
		if (allocation->pLine & mask)
			continue;
		prynPrintFormat (state, "\t%s(%d): %d byte(s)\n", allocation->pFilename ? allocation->pFilename : "<unknown file>", allocation->pLine, allocation->pSize);
		allocation->pLine |= mask;
	}
	
	return PrynResultSuccess;
#endif
}

PrynExport (PrynResult, prynReallocate, (PrynState *state, size_t length, void **output))
{
	PrynStartFunctionHasState ();

	prynAssert (state && length, NullArgument);
	if (*output)
	{
		if (length)
		{
			void *block;

			prynCall (prynAllocate (state, length, &block));
			prynCopyMemory (block, *output, length);
			*output = block;
		}
		else
		{
			prynCall (prynFree (state, *output));
			*output = 0;
		}
	}
	else if (length)
		prynCall (prynAllocate (state, length, output));
	else
		*output = 0;

	PrynEndFunction ();
}

#ifndef PrynAllocationTracker
	PrynExport (PrynResult, prynAllocate, (PrynState *state, uintptr_t length, void **output))
	{
		if (output) *output = 0;
		PrynStateFunction ({
			prynAssert (output, NullArgument);
			if (length) return state->pAllocateFunction (state, length, output);
		});
	}
	
	PrynExport (PrynResult, prynFree, (PrynState *state, void *pointer))
	{
		if (!state) return PrynResultNullArgument;
		if (!pointer) return PrynResultDone;
		return state->pFreeFunction (state, pointer);
	}
	
	PrynExport (PrynResult, prynDuplicateData, (PrynState *state, const void *source, uintptr_t length, void **output))
	{
		if (output) *output = 0;
		PrynStateFunction ({
			void *target;

			prynAssert (source && output, NullArgument);
			prynCall (prynAllocate (state, length, &target));
			prynCopyMemory (target, source, length);
			*output = target;
		});
	}
	
	PrynExport (PrynResult, prynAllocateCleared, (PrynState *state, uintptr_t length, void **output))
	{
		if (output) *output = 0;
		PrynStateFunction ({
			void *target;

			prynAssert (output, NullArgument);
			prynCall (prynAllocate (state, length, &target));
			prynClearMemory (target, length);
			*output = target;
		});
	}
	
	// The following are in case a DLL was linked with allocation tracking enabled.
	
	PrynExport (PrynResult, prynAllocateTracked, (PrynState *state, uintptr_t length, void **output, const char *filename, size_t line))
	{
		return prynAllocate (state, length, output);
	}
	
	PrynExport (PrynResult, prynAllocateClearedTracked, (PrynState *state, uintptr_t length, void **output, const char *filename, size_t line))
	{
		return prynAllocateCleared (state, length, output);
	}
	
	PrynExport (PrynResult, prynDuplicateDataTracked, (PrynState *state, const void *source, uintptr_t length, void **output, const char *filename, size_t line))
	{
		return prynDuplicateData (state, source, length, output);
	}
#else /* PrynAllocationTracker */
	PrynExport (void *, prynAllocateTracked, (PrynState *state, uintptr_t length, const char *filename, size_t line))
	{
		if (!state || !length) return 0;
		PrynAllocation *allocation = state->pAllocateFunction (state, length + sizeof (PrynAllocation));
		if (!allocation) return 0;
		allocation->pFilename = filename;
		allocation->pLine = line;
		allocation->pSize = length;
		state->pAllocatedCurrent += allocation->pSize;
		prynMaximumAssign (state->pAllocatedMaximum, state->pAllocatedCurrent);
		prynDListAppend (state->pAllocations, allocation);
		return allocation + 1;
	}
	
	PrynExport (PrynResult, prynFree, (PrynState *state, void *pointer))
	{
		if (!pointer || !state) return PrynResultNullArgument;
		PrynAllocation *allocation = ((PrynAllocation *) pointer) - 1;
		state->pAllocatedCurrent -= allocation->pSize;
		prynDListRemove (state->pAllocations, allocation);
		state->pFreeFunction (state, pointer);
		return PrynResultSuccess;
	}
	
	PrynExport (void *, prynAllocateClearedTracked, (PrynState *state, uintptr_t length, const char *filename, size_t line))
	{
		void *target = prynAllocateTracked (state, length, filename, line);
		
		if (!target) return 0;
		prynSetMemory (target, 0, length);
		return target;
	}
	
	PrynExport (void *, prynDuplicateDataTracked, (PrynState *state, const void *source, uintptr_t length, const char *filename, size_t line))
	{
		void *target = prynAllocateTracked (state, length, filename, length);
		
		if (!target) return 0;
		prynCopyMemory (target, source, length);
		return target;
	}
	
	// The following are for linking to DLLs which have not been compiled with allocation tracking. The brackets around the name are necessary to prevent them from being used as macros.
	
	PrynExport (void *, (prynAllocate), (PrynState *state, uintptr_t length))
	{
		return prynAllocateTracked (state, length, 0, 0);
	}
	
	PrynExport (void *, (prynAllocateCleared), (PrynState *state, uintptr_t length))
	{
		return prynAllocateClearedTracked (state, length, 0, 0);
	}
	
	PrynExport (void *, (prynDuplicateData), (PrynState *state, const void *source, uintptr_t length))
	{
		return prynDuplicateDataTracked (state, source, length, 0, 0);
	}
#endif /* PrynAllocationTracker */

// Complete function definition for iterating over a list in state. PLURAL is such as Factories; SINGULAR is such as Factory. GETSTATE is the field reflecting the state in the item.
#define PrynStateIterateFunction(PLURAL, SINGULAR, GETSTATE) \
	PrynExport (PrynResult, prynStateIterate##PLURAL, (PrynState *state, Pryn##SINGULAR **item)) \
	{ \
		if (!state || !item) { if (item) *item = 0; return PrynResultNullArgument; } \
		if (*item) { if ((*item)->GETSTATE != state) return PrynResultBadArgument; (*item) = (*item)->pNext; } \
		else (*item) = state->p##PLURAL.first; \
		if (*item) return PrynResultSuccess; \
		return PrynResultDone; \
	}

PrynStateIterateFunction (Factories, Factory, pLibrary->pState);
PrynStateIterateFunction (Libraries, Library, pState);
PrynStateIterateFunction (Mediators, Mediator, pLibrary->pState);
PrynStateIterateFunction (Types, Type, pLibrary->pState);